package tests;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import src.CD;
import src.Item;
import src.Library;
import src.UserAccount;

public class TestLibrary {

	private Library objectOfClassUnderTest;

	private Item item;

	private UserAccount user;

	@Before
	public void setUp() throws Exception {
		objectOfClassUnderTest = Library.getTheLibrary();

		item = new CD("title", "author", "01.01.1978");
		user = new UserAccount("name", "pi", "mail@mail.de");

		objectOfClassUnderTest.addUserAccount(user);
		objectOfClassUnderTest.addItem(item);
	}

	@After
	public void tearDown() throws Exception {
		// objectOfClassUnderTest.returnItem(item, user);
		objectOfClassUnderTest.removeUserAccount(user);
		objectOfClassUnderTest.removeItem(item);

		objectOfClassUnderTest = null;

		item = null;
		user = null;

		System.gc();
	}

	@Test
	public final void testGetTheLibrary() {
		Library lib2 = Library.getTheLibrary();

		assertSame(
				"The Library returned by getTheLibrary should always be the same library!",
				objectOfClassUnderTest, lib2);
	}

	@Test
	public final void testBorrowItemSuccessful() {
		assertTrue("Borrowing an item was not successful!",
				objectOfClassUnderTest.borrowItem(item, user));
		objectOfClassUnderTest.returnItem(item, user);
	}

	@Test
	public void testBorrowItemUnsuccessfulBecauseOfTwoMuchItemsBorrowed()
			throws Exception {
		Item item1 = new CD("1", "1", "01.01.1978"), item2 = new CD("2", "2",
				"01.01.1978"), item3 = new CD("3", "3", "01.01.1978"), item4 = new CD(
				"4", "4", "01.01.1978"), item5 = new CD("5", "5", "01.01.1978");

		objectOfClassUnderTest.addItem(item1);
		objectOfClassUnderTest.addItem(item2);
		objectOfClassUnderTest.addItem(item3);
		objectOfClassUnderTest.addItem(item4);
		objectOfClassUnderTest.addItem(item5);

		assertTrue("Borrowing an item (Item 1) was not successful!",
				objectOfClassUnderTest.borrowItem(item1, user));
		assertTrue("Borrowing an item (Item 2) was not successful!",
				objectOfClassUnderTest.borrowItem(item2, user));
		assertTrue("Borrowing an item (Item 3) was not successful!",
				objectOfClassUnderTest.borrowItem(item3, user));
		assertTrue("Borrowing an item (Item 4) was not successful!",
				objectOfClassUnderTest.borrowItem(item4, user));
		assertTrue("Borrowing an item (Item 5) was not successful!",
				objectOfClassUnderTest.borrowItem(item5, user));
		assertTrue("Returning an item (Item 5) was not successful!",
				objectOfClassUnderTest.returnItem(item5, user));
		assertTrue("Borrowing an item (Item 5) was not successful!",
				objectOfClassUnderTest.borrowItem(item5, user));

		assertFalse("No user may borrow more than five items!",
				objectOfClassUnderTest.borrowItem(item, user));

		// user now can borrow again 5 items! -> that FIX the BUG with
		// "testBorrowItemUnsuccessfulBecauseOfItemAlreadyBorrowed()"
		objectOfClassUnderTest.returnItem(item1, user);
		objectOfClassUnderTest.returnItem(item2, user);
		objectOfClassUnderTest.returnItem(item3, user);
		objectOfClassUnderTest.returnItem(item4, user);
		objectOfClassUnderTest.returnItem(item5, user);
	}

	// Bug: Can be only considered for Sets
	/**
	 * @Test public void
	 *       testBorrowItemUnsuccessfulBecauseOfItemAlreadyBorrowed() throws
	 *       Exception { UserAccount user2 = new UserAccount("name2", "pi2",
	 *       "mail2@mail.de"); objectOfClassUnderTest.addUserAccount(user2);
	 *       objectOfClassUnderTest.borrowItem(item, user2);
	 *       assertFalse("It should not be possible to borrow a borrowed item",
	 *       objectOfClassUnderTest.borrowItem(item, user));
	 * 
	 *       //That FIX the bug with "testReturnItemSuccessful()"
	 *       objectOfClassUnderTest.returnItem(item, user2); }
	 * 
	 *       // New Test
	 * @Test public void
	 *       testBorrowItemUnsuccessfulBecauseOfItemOrUserIsNotKnownByTheLibrary
	 *       () { Item notKnownItem = new CD("not", "known", "01.01.1978");
	 *       assertFalse(
	 *       "It should not be possoble to borrow a item which is not known by the library."
	 *       , objectOfClassUnderTest.borrowItem(notKnownItem, user));
	 * 
	 *       UserAccount notKnownUser = new UserAccount("not", "known",
	 *       "not-known@library.test");
	 * 
	 *       assertFalse(
	 *       "It should not be possible that a not registered user borrow items."
	 *       , objectOfClassUnderTest.borrowItem(item, notKnownUser)); }
	 */

	@Test
	public void testBorrowItemUnsuccessfulBecauseOfTooHighOverdraftFee()
			throws Exception {
		this.user.addToAccumulatedOverdraftFee(100.1);
		assertFalse(
				"It should not be possible to borrow a new item from the library if the accumulated overdraft fee of the user is higher than 100 EUR!",
				objectOfClassUnderTest.borrowItem(item, user));
	}

	@Test
	public void testBorrowItemWithNullArgument() throws Exception {
		try {
			objectOfClassUnderTest.borrowItem(null, user);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}

		try {
			objectOfClassUnderTest.borrowItem(item, null);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}

		try {
			objectOfClassUnderTest.borrowItem(null, null);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}
	}

	@Test
	public final void testReturnItemSuccessful() {
		objectOfClassUnderTest.borrowItem(item, user);
		assertTrue(
				"A borrowed Item may be returned, with the returnItem method returning true!",
				objectOfClassUnderTest.returnItem(item, user));
	}

	@Test
	public void testReturnItemUnsuccessful() throws Exception {
		Item notBorrowedItem = new CD("title2", "author", "01.01.1978");
		objectOfClassUnderTest.addItem(notBorrowedItem);

		assertFalse(
				"Libraries returnItem method should return false if the item can not be returned because it is not borrowed!",
				this.objectOfClassUnderTest.returnItem(notBorrowedItem, user));

		objectOfClassUnderTest.removeItem(notBorrowedItem);
	}

	@Test
	public void testReturnItemWithNullArgument() throws Exception {
		objectOfClassUnderTest.borrowItem(item, user);
		try {
			objectOfClassUnderTest.returnItem(null, user);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}

		try {
			objectOfClassUnderTest.borrowItem(item, null);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}

		try {
			objectOfClassUnderTest.borrowItem(null, null);
			fail("A NullPointerException should be thrown!");
		} catch (NullPointerException exception) {

		}
		objectOfClassUnderTest.returnItem(item, user);
	}

}
